Borland Online And The Cobb Group Present:


February, 1996 - Vol. 3 No. 2

C++ Language Basics - Applying multiple inheritance

by Tim Gooch

This article is the last in a three-part series. It has been adapted from "Using Multiple Inheritance Effectively" in Black Belt C++ (MIS:Press, 1994), (800) 628-9658.

In last month's issue of Borland C++ Developer's Journal, we described some of the important design issues that you need to consider when you implement multiple inheritance (C++ Language Basics - Using multiple inheritance January 1996). This month, we'll examine a few of the issues that can complicate using multiple inheritance in real projects. It's important to remember that these issues won't come into play very often if you use multiple inheritance primarily in the ways we discussed last month.

Name clashes

One of the first concerns you'll hear when discussing multiple inheritance is, "What if two of the base classes have members with the same name?" While this situation occurs only infrequently, at times you will want to inherit from two or more classes that contain identically named class members (either member functions or data members). There are really two problems here: simple name clashes (in which non-virtual member functions or data members have the same name) and virtual function name clashes.

Simple name clashes

If the conflict involves a non-virtual member function or a data member, you can use the fully qualified name of the member to access the correct element. For example, consider last month's example of using the RefCount add-in class and the Window class to create the RefCountWin class (a reference-counted window object).

Internally, the RefCount class keeps track of the number of smart pointers that are referring to it (the reference count) with the data member _rc. In the unlikely event that the parent class Window also contains a member named _rc, all the member functions of the Window class that refer to _rc will automatically use Window::_rc instead of RefCount::_rc unless you call them explicitly from a member function in the RefCountWin class. (Since the only reason for creating the RefCountWin class is to attach a reference count to each Window object, you shouldn't be overriding functions or adding members directly in this class anyway.)

Second, since all of the smart pointer member functions that will change the value of _rc will expect a pointer or reference to a RefCount object, the compiler will implicitly convert a pointer or reference to a RefCountWin object to one for a RefCount object. Once again, since these functions don't know about the surrounding RefCountWin object, any member function that uses the name _rc will automatically use RefCount::_rc instead of Window::_rc.

If you need to override some of the virtual functions from base classes that contain identically named class members, use the full name of the data member or member function. For example, if you override a virtual function from the Window class in the RefCountWin class, you'll use the name Window::_rc to access this member. (By the way, making the _rc member private or using private derivation won't help eliminate the name clash. The compiler will check for a naming conflict before checking for accessibility.)

Also, remember that for any member function that exists in both classes, the function names and argument list types must match exactly to create a name clash. If they don't match, the derived class will contain both functions, they will overload each other based on the arguments you use when you call them, and no clash will exist.

If the name in question is part of the public section of either class (which means other functions or classes will probably try to call it directly), all bets are off, and you definitely have a name clash. However, once again, you'll usually be able to use the fully qualified name of the data member or member function to call the correct version. The only time you won't want to do this is when the name clash involves a virtual member function.

Virtual function name clashes

If you use virtual functions frequently, you may be surprised to realize that most naming conflicts you might expect to occur with data members and non-virtual functions won't occur in the base class's member functions. You might not expect this since the virtual function mechanism makes sure that the sub-objects in a derived class object know about the virtual functions that the derived class overrides. However, if the derived class doesn't override any of the base class functions, there typically won't be a problem because each sub-object's vtable will contain only the addresses of functions that it inherits or defines, or functions that the derived class overrides and defines.

All this holds true if you're not using virtual bases. As we saw in last month's discussion of mix-in class hierarchies, using a virtual base can create a clash because the vtables on either side of the inheritance diagram will know about the functions defined on the other side.

As was the case with data members and non-virtual member functions, you can use a fully qualified name to call the correct virtual function. The downside is that when you do, the call is no longer virtual. (Whether or not any further derived classes override it, the code that uses the fully qualified name will call the version of the function defined in that particular class.) The question becomes, How can you call one virtual function instead of the other when they both have the same name and argument list types, without defeating the purpose of the virtual function mechanism?

The solution calls for creating an intermediate class for each base class that contains the name that clashes. In the intermediate classes, you'll simply override the clashing virtual function and call a new pure virtual function that has a new non-clashing name for the old function. In the derived class that brings together these intermediate classes, you'll define the new behavior you want for the clashing virtual functions by overriding the new pure virtual functions.

Now, any call to the original function will eventually call the newly named virtual function. It doesn't matter where the call comes from­­as long as you don't try to call it from the new derived class (once again, you'd have a name clash). Even member functions from a base class many derivations up the inheritance hierarchy will call the new version indirectly via the old name. In this way, further derivations of the class that contains the clashing virtual function names can continue to extend and enhance the behavior of the clashing virtual functions.

Add-in functions vs. parasitic virtual functions

It's not uncommon for an overriding virtual function to call the base class version of the same function. If you're deriving a new class from two or more classes that come from a common base class, you might be inclined to call these two different versions of the function to make sure that each part of the object has its turn to respond to the function call. However, doing so can cause problems when you're using virtual base classes.

Consider the traditional object-oriented programming CheckingAccount, SavingsAccount example, where you've derived these two classes from a BaseAccount class. To create an interest-bearing checking account (a Negotiable Order of Withdrawal or NOW account), we'll derive a NowAccount class from the CheckingAccount and SavingsAccount classes, and we'll make sure the BaseAccount class is a virtual base class of those classes.

Now, consider what happens if we've defined a virtual function PrintAccountSummary() in each class. The BaseAccount version of this function would probably print the account number and the balance. The CheckingAccount version would call the BaseAccount version of the function first and then would print a list of checks written from the account during the past month.

Finally, the SavingsAccount version would call the BaseAccount version and then would display the account's current interest rate and the interest credited to the account during the past month. The following code illustrates the base classes' behavior:

void BaseAccount::PrintAccountSummary()
{ cout << "Account #" << accountNum << endl; 
  cout << "Balance - $" << balance() << endl; }


void CheckingAccount::PrintAccountSummary()
{ BaseAccount::PrintAccountSummary(); 
  // call base class version
  for(checkCount = 0;
      checkCount < numChecks;
      ++checkCount)
      { cout << "#" << checks[checkCount].number;
        cout << " - $" 
          << checks[checkCount].amount << end;
      } 
}

void SavingsAccount::PrintAccountSummary()
{ BaseAccount::PrintAccountSummary(); 
  // call base class version
  cout << "Interest Rate - %" << interestRate 
     << endl;
  cout << "Interest Paid - $" 
    << InterestCredited() << endl; }

When we create the NowAccount class, we'll want to provide a version of the PrintAccountSummary() virtual function that will correctly display all the same data that the base class versions display. However, if we write

void NowAccount::PrintAccountSummary()
{ // base class versions
  CheckingAccount::PrintAccountSummary(); 
  SavingsAccount::PrintAccountSummary(); }

we'll see something similar to the following output:

Account #1234567
Balance - $266.19
Account #1234567
Balance - $266.19
Checks for this month:
#2330 -  $13.02
----------
Interest Rate - %3.2
Interest Paid - $1.92

Why did the function call the BaseAccount::PrintAccountSummary() function twice? Because the intermediate account classes both called the base version of the function.

Once again, if you look closely at the PrintAccountSummary() functions in the CheckingAccount and SavingsAccount classes, you'll find that they do something useful in addition to calling the base class version of the function. Because these functions don't allow you to separate these useful actions from the action of the BaseAccount version, we'll call them parasitic virtual functions.

In much the same way that we moved the members from a parasitic class and placed them into an add-in class last month, you can introduce class-specific implementation functions called add-in functions. To create an add-in function, just take the code that the derived class's function would have added, and place it into a separate non-virtual protected function, just as we placed behavior or attributes that a parasitic class added into an add-in class. Once you've created the add-in function, you'll change the companion virtual function to call first the base class and then the companion function.

In our example, you'd add the following protected non-virtual function to the CheckingAccount class:

void CheckingAccount::_PrintAccountSummary()
{ for(checkCount = 0;
      checkCount < numChecks;
      ++checkCount)
     { cout << "#" << checks[checkCount].number;
       cout << " - $" << check[checkCount].amount     	<< end;
     } 
}

Then, we'd write a similar version for the SavingsAccount class to print the interest data. Now, we can rewrite the NowAccount class's version of the PrintAccountSummary() function:

void NowAccount::PrintAccountSummary()
{ BaseAccount::PrintAccountSummary(); 
  // call base class version
  CheckingAccount::_PrintAccountSummary();
  SavingsAccount::_PrintAccountSummary(); }

After you make this change, a NowAccount object will print the BaseAccount data only once when you call the PrintAccountSummary() function. Interestingly, this technique works even if you're not using virtual base classes. However, the two BaseAccount sub-objects may not contain the same data (you may be making deposits to and writing checks from one BaseAccount sub-object, and crediting interest in the other), causing some unexpected behavior.

Most derived class

In the previous section, we discussed how you can avoid calling a virtual function in a virtual base class multiple times. Now, consider what happens during construction of a NowAccount object (when BaseAccount is a virtual base class). Since the constructors for the CheckingAccount and SavingsAccount classes initialize the BaseAccount class, you might wonder why the constructor for the NowAccount class needs to initialize the BaseAccount class, and why the compiler doesn't initialize it multiple times.

First, when a class contains one or more virtual base classes, the compiler will always initialize those base classes first. Second, the compiler will require the outermost enclosing object or most derived class to initialize the virtual base classes. By doing so, and then ignoring all other initializers, the compiler can guarantee that it will build the virtual base sub-object only once.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.